package cn.edu.sgu.www.mhxysy.location.util;

import cn.edu.sgu.www.mhxysy.config.property.SystemSettingsProperties;
import cn.edu.sgu.www.mhxysy.location.entity.Location;
import cn.edu.sgu.www.mhxysy.util.StringUtils;
import com.alibaba.fastjson.JSON;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.util.UriUtils;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 地理位置信息工具类
 * @author 沐雨橙风ιε
 * @version 1.0
 */
@Component
public class LocationUtils {

    /**
     * 应用AK
     */
    private static String AK;

    /**
     * 应用SK
     */
    private static String SK;

    /**
     * 百度地图api的地址
     */
    public static String URL = "https://api.map.baidu.com/location/ip?";

    @Autowired
    public LocationUtils(SystemSettingsProperties systemSettingsProperties) {
        AK = systemSettingsProperties.getAk();
        SK = systemSettingsProperties.getSk();
    }

    /**
     * 根据IP获取定位信息
     * @param ip IP地址
     * @return Location
     * @throws Exception getLocation(String, String)抛出的异常
     */
    public static Location getLocation(String ip) throws Exception {
        Map<String, String> params = new LinkedHashMap<>(4);

        params.put("ip", ip);
        params.put("coor", "bd09ll");
        params.put("ak", AK);
        params.put("sn", getSn(ip));

        String location = getLocation(URL, params);

        return JSON.parseObject(StringUtils.toUpper(location), Location.class);
    }

    private static String getLocation(String url, Map<String, String> param) throws Exception {
        if (url == null || url.length() <= 0 || param == null || param.size() <= 0) {
            return null;
        }

        StringBuilder queryString = new StringBuilder();

        queryString.append(url);

        for (Map.Entry<?, ?> pair : param.entrySet()) {
            queryString.append(pair.getKey()).append("=");
            // 第一种方式使用的 jdk 自带的转码方式  第二种方式使用的 spring 的转码方法 两种均可
            // queryString.append(URLEncoder.encode((String) pair.getValue(), "UTF-8").replace("+", "%20") + "&");
            queryString.append(UriUtils.encode((String) pair.getValue(), "UTF-8")).append("&");
        }

        if (queryString.length() > 0) {
            queryString.deleteCharAt(queryString.length() - 1);
        }

        URL apiUrl = new URL(queryString.toString());
        URLConnection connection = apiUrl.openConnection();

        connection.connect();

        // 连接各种流
        InputStream inputStream = connection.getInputStream();
        InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
        BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

        // 读取数据
        StringBuilder sb = new StringBuilder();
        String line;

        while ((line = bufferedReader.readLine()) != null) {
            sb.append(line);
        }

        bufferedReader.close();
        inputStreamReader.close();

        return sb.toString();
    }

    /**
     * 生成sn的计算代码
     * @param ip IP地址
     */
    private static String getSn(String ip) throws UnsupportedEncodingException {
        // 计算sn跟参数对出现顺序有关，get请求请使用LinkedHashMap保存<key,value>，该方法根据key的插入顺序排序；post请使用TreeMap保存<key,value>，该方法会自动将key按照字母a-z顺序排序。
        // 所以get请求可自定义参数顺序（sn参数必须在最后）发送请求，但是post请求必须按照字母a-z顺序填充body（sn参数必须在最后）。
        // 以get请求为例：http://api.map.baidu.com/geocoder/v2/?address=百度大厦&output=json&ak=yourak，paramsMap中先放入address，再放output，然后放ak，放入顺序必须跟get请求中对应参数的出现顺序保持一致。
        Map<String, String> paramsMap = new LinkedHashMap<>();

        paramsMap.put("ip", ip);
        paramsMap.put("coor", "bd09ll");
        paramsMap.put("ak", AK);

        // 调用下面的toQueryString方法，对LinkedHashMap内所有value作utf8编码，拼接返回结果address=%E7%99%BE%E5%BA%A6%E5%A4%A7%E5%8E%A6&output=json&ak=yourak
        String paramsStr = toQueryString(paramsMap);

        // 对paramsStr前面拼接上/geocoder/v2/?，后面直接拼接yoursk得到/geocoder/v2/?address=%E7%99%BE%E5%BA%A6%E5%A4%A7%E5%8E%A6&output=json&ak=yourakyoursk
        String wholeStr = "/location/ip?" + paramsStr + SK;
        // 对上面wholeStr再作utf8编码
        String tempStr = URLEncoder.encode(wholeStr, "UTF-8");

        // 调用下面的MD5方法得到最后的sn签名
        return MD5(tempStr);
    }

    /**
     * 来自stackoverflow的MD5计算方法，调用了MessageDigest库函数，并把byte数组结果转换成16进制
     * @param md5 String
     * @return String
     */
    private static String MD5(String md5) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] array = md.digest(md5.getBytes());
            StringBuilder sb = new StringBuilder();

            for (byte b : array) {
                sb.append(Integer.toHexString((b & 0xFF) | 0x100), 1, 3);
            }

            return sb.toString();
        } catch (java.security.NoSuchAlgorithmException ignored) { }

        return null;
    }

    /**
     * 对Map内所有value作utf8编码，拼接返回结果
     * @param map Map<String, String>
     * @return String
     */
    private static String toQueryString(Map<String, String> map) {
        StringBuilder queryString = new StringBuilder();

        for (Map.Entry<String, String> entry : map.entrySet()) {
            queryString.append(entry.getKey()).append("=");
            // 第一种方式使用的 jdk 自带的转码方式  第二种方式使用的 spring 的转码方法 两种均可
            // queryString.append(URLEncoder.encode((String) entry.getValue(), "UTF-8").replace("+", "%20") + "&");
            queryString.append(UriUtils.encode(entry.getValue(), "UTF-8")).append("&");
        }

        if (queryString.length() > 0) {
            queryString.deleteCharAt(queryString.length() - 1);
        }

        return queryString.toString();
    }

}